﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System;
using System.IO;
using UnityEngine.SceneManagement;

namespace Kylin
{
    public class ResourceLoader
    {
        private static Dictionary<string, BundleInfo> loadedBundle = new Dictionary<string, BundleInfo>();
        private static AssetBundleManifest bundleManifest;
        private static readonly string sceneFolderName = "Scene";
        private static readonly string defaultBundleSuffix = ".nm";

        public static string LoadTable(string path)
        {
            return null;
        }

        public static byte[] LoadLua(string path)
        {
            try
            {
                string script = ResourcePath.LuaFolderPath + "/" + path.Replace(".", "/");
                if (false == script.EndsWith(ConstDefines.LuaExt))
                {
                    script += ConstDefines.LuaExt;
                }
#if UNITY_EDITOR
                bool useResources = true;

                useResources = PlayerPrefs.GetString("ResourceLoadTypeKey") == "Resources";
                if (!useResources)
                {
                    script += ".txt";
                }

                byte[] result = null;

                if (File.Exists(script))
                {
                    FileStream fs = File.Open(script, FileMode.Open);
                    if (fs != null)
                    {
                        long length = fs.Length;
                        byte[] bytes = new byte[length];
                        fs.Read(bytes, 0, bytes.Length);
                        fs.Close();

                        if (ConstDefines.EncryptLua)
                        {
                            FileUtils.EncryptAll(ref bytes);
                        }

                        using (Stream stream = new MemoryStream(bytes))
                        {
                            using (StreamReader sr = new StreamReader(stream))
                            {
                                string code = sr.ReadToEnd();
                                result = System.Text.Encoding.UTF8.GetBytes(code);
                            }
                        }
                    }
                }
                else
                {
                    UnityEngine.Debug.LogError("[error] Cant find " + script);
                }
                return result;
#else
                if (path.EndsWith(".lua"))
                {
                    UnityEngine.Debug.LogError("[Error] File not allowed .lua extension" + path);
                    return null;
                }

                byte[] result = null;                   
                string luafile = path + ".lua.txt";
                string fullpath = ResourcePath.LuaFolderPath + luafile;

                if(File.Exists(fullpath))
                {
                    FileStream fs = System.IO.File.Open(fullpath, FileMode.Open);
                    if(fs != null)
                    {
                        long length = fs.Length;
                        byte[] bytes = new byte[length];
                        fs.Read(bytes, 0, bytes.Length);
                        fs.Close();

                        if (ConstDefines.EncryptLua)
                        {
                            FileUtils.EncryptAll(ref bytes);
                        }

                        using (Stream stream = new MemoryStream(bytes))
                        {
                            using (StreamReader sr = new StreamReader(stream))
                            {
                                string code = sr.ReadToEnd();
                                result = System.Text.Encoding.UTF8.GetBytes(code);
                            }
                        }
                    }
                }

                return result;
#endif
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError(e.Message + "  " + e.StackTrace);
                return null;
            }
        }

        public static AssetBundleManifest BundleManifest
        {
            get
            {
                if (bundleManifest == null)
                {
                    if (!File.Exists(ResourcePath.BundleFilePath))
                    {
                        Debug.LogWarning("是不是项目没有真的开始，找不到相应的资源（manifest依赖文件）。如果是真的项目这就是个错误，否则请忽略。");
                    }

                    try
                    {
                        AssetBundle ab = AssetBundle.LoadFromFile(ResourcePath.BundleFilePath);
                        bundleManifest = ab.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
                    }
                    catch (Exception e1)
                    {
                        Debug.LogException(e1);
                        Debug.Log(e1.StackTrace);
                    }

                    if (bundleManifest == null)
                    {
                        throw new Exception("Cannot get valid manifest file for asset bundle!");
                    }
                    else
                    {
                        Debug.Log(string.Format("Manifest file: {0} loaded successfully.", ResourcePath.BundlePath));
                    }
                }

                return bundleManifest;
            }
        }

        public static UnityEngine.Object LoadAsset(string path)
        {
#if UNITY_EDITOR
            bool useResources = true;

            useResources = PlayerPrefs.GetString("ResourceLoadTypeKey") == "Resources";
            if (useResources)
            {
                path = "HotFix/" + path;
                var res = Resources.Load(path);
                return res;
            }
            else
            {
#endif

#if LOG_RESOURCE_LOAD_UNLOAD
                Debug.Log("load from asset bundle, path: " + path);
#endif

                path = AddVariant(path);

                string[] dependencies = BundleManifest.GetAllDependencies(path);
                for (int i = 0; i < dependencies.Length; ++i)
                {
                    if (!loadedBundle.ContainsKey(dependencies[i]))
                    {
                        LoadAsset(dependencies[i]);
                    }
                    else
                    {
                        loadedBundle[dependencies[i]].ReferenceCount++;
                        PrintRefererenCount(dependencies[i]);
                        return loadedBundle[dependencies[i]].ObjectHandle;
                    }
                }

                if (loadedBundle.ContainsKey(path))
                {
                    loadedBundle[path].ReferenceCount++;
                    PrintRefererenCount(path);
                    return loadedBundle[path].ObjectHandle;
                }

                string group, name;
                string bundleFile = MakeBundleFullPath(path, out group, out name);
                AssetBundle ab = AssetBundle.LoadFromFile(bundleFile);
                return BundleLoaded(ab, path, group, name);
#if UNITY_EDITOR
            }
#endif
        }

        private static UnityEngine.Object BundleLoaded(AssetBundle ab, string path, string group, string name)
        {
            if (ab != null)
            {
                UnityEngine.Object target;
                if (string.IsNullOrEmpty(group))
                {
                    UnityEngine.Object[] b = ab.LoadAllAssets();
                    target = b[0];
                }
                else
                {
                    target = ab.LoadAsset(name);
                }

                if (target == null)
                {
                    Debug.LogError("LoadAsset failed, path: " + path);
                }

                BundleInfo bundleInfo = new BundleInfo();
                bundleInfo.AssetBundleHandle = ab;
                bundleInfo.ObjectHandle = target;
                bundleInfo.ReferenceCount++;
                loadedBundle[path] = bundleInfo;
                PrintRefererenCount(path);

                return target;
            }
            else
            {
                Debug.LogError("Load assetbundle [" + path + " ] failed!");
                return null;
            }
        }

        public static void LoadAssetAsync(string path, Action<UnityEngine.Object> asyncResult)
        {
            MonoBeBehaviourManager.Instance.StartCoroutine(LoadBundleAsync(path, asyncResult));
        }

        private static IEnumerator LoadBundleAsync(string path, Action<UnityEngine.Object> asyncResult)
        {
            path = AddVariant(path);

            string[] dependencies = BundleManifest.GetAllDependencies(path);
            for (int i = 0; i < dependencies.Length; ++i)
            {
                if (!loadedBundle.ContainsKey(dependencies[i]))
                {
                    yield return MonoBeBehaviourManager.Instance.StartCoroutine(LoadBundleAsync(dependencies[i], null));
                }
                else
                {
                    loadedBundle[dependencies[i]].ReferenceCount++;
                    PrintRefererenCount(dependencies[i]);
                }
            }

            if (loadedBundle.ContainsKey(path))
            {
                loadedBundle[path].ReferenceCount++;
                PrintRefererenCount(path);
                if (asyncResult != null)
                {
                    asyncResult(loadedBundle[path].ObjectHandle);
                }
                yield break;
            }

            string group, name;
            path = MakeBundleFullPath(path, out group, out name);
            var bundleLoadRequest = AssetBundle.LoadFromFileAsync(path);
            yield return bundleLoadRequest;

            var ab = bundleLoadRequest.assetBundle;
            UnityEngine.Object target = BundleLoaded(ab, path, group, name);
            if (asyncResult != null)
            {
                asyncResult(target);
            }
        }

        private static string MakeBundleFullPath(string path, out string group, out string name)
        {
            string bundleFile;
            name = string.Empty;
            if (BundleConfig.Instance().ContainsInFolder(path, out group))
            {
                bundleFile = string.Format("{0}/{1}", ResourcePath.BundlePath, group); ;
                name = path.Remove(0, group.Length);
                if (!string.IsNullOrEmpty(name) && name.StartsWith("/"))
                {
                    name = name.Remove(0, 1);
                }
            }
            else
            {
                bundleFile = string.Format("{0}/{1}", ResourcePath.BundlePath, path);
            }
            return bundleFile;
        }

        //单开一个接口，为了资源统一管理
        public static string LoadSceneAsset(string sceneName)
        {
            string bundlePath = MakeScenePath(sceneName);
            string fullPath = ResourcePath.BundlePath + "/" + bundlePath;
            string[] depends = BundleManifest.GetAllDependencies(bundlePath);
            for (int i = 0; i < depends.Length; ++i)
            {
                LoadAsset(depends[i]);
            }

            // scene不能重复加载，在SceneLoader中需要先判断
            AssetBundle ab = AssetBundle.LoadFromFile(fullPath);
            if (ab == null)
            {
                Debug.LogError("Load scene assetbundle [" + fullPath + " ] failed!");
                return null;
            }
            else
            {
                string[] paths = ab.GetAllScenePaths();
                string path = Path.GetFileNameWithoutExtension(paths[0]);

                BundleInfo bundleInfo = new BundleInfo();
                bundleInfo.AssetBundleHandle = ab;
                bundleInfo.ObjectHandle = null;
                bundleInfo.ReferenceCount++;
                loadedBundle[bundlePath] = bundleInfo;
                PrintRefererenCount(bundlePath);

                return path;
            }
        }

        public static void UnloadSceneAsset(string sceneName)
        {
            string bundlePath = MakeScenePath(sceneName);
            string[] dependencies = BundleManifest.GetAllDependencies(bundlePath);
            for (int index = 0; index < dependencies.Length; ++index)
            {
                UnLoadAsset(dependencies[index]);
            }

            if (loadedBundle.ContainsKey(bundlePath))
            {
                loadedBundle[bundlePath].ReferenceCount--;
                PrintRefererenCount(bundlePath);
                if (loadedBundle[bundlePath].ReferenceCount == 0)
                {
                    loadedBundle[bundlePath].AssetBundleHandle.Unload(false);
                    loadedBundle.Remove(bundlePath);
#if LOG_RESOURCE_LOAD_UNLOAD
                    Debug.LogFormat("<color=#FFFF00>卸载场景资源：{0} </color>", bundlePath);
#endif
                }
            }
        }

        public static string MakeScenePath(string sceneName)
        {
            return sceneFolderName + "/" + sceneName + defaultBundleSuffix;
        }

        private static void PrintRefererenCount(string key)
        {
#if LOG_RESOURCE_LOAD_UNLOAD
            Debug.Log("资源：" + key + "计数器现在的值是: " + loadedBundle[key].ReferenceCount);
#endif
        }

        [LuaCallCSharp]
        public static void UnLoadAsset(string path, bool force = false)
        {
#if UNITY_EDITOR
            bool useResources = true;

            useResources = PlayerPrefs.GetString("ResourceLoadTypeKey") == "Resources";
            if (useResources)
            {
                
            }
            else
            {
#endif
                path = AddVariant(path);

                string[] dependencies = BundleManifest.GetAllDependencies(path);
                for (int i = 0; i < dependencies.Length; ++i)
                {
                    UnLoadAsset(dependencies[i], force);
                }

                if (loadedBundle.ContainsKey(path))
                {
                    loadedBundle[path].ReferenceCount--;
                    PrintRefererenCount(path);
                    if (loadedBundle[path].ReferenceCount == 0)
                    {
                        loadedBundle[path].AssetBundleHandle.Unload(force);
                        loadedBundle.Remove(path);
#if LOG_RESOURCE_LOAD_UNLOAD
                        Debug.LogFormat("<color=#FFFF00>卸载资源：{0} </color>", path);
#endif
                    }
                }
#if UNITY_EDITOR
            }
#endif
        }

        private static string AddVariant(string path, int variant = 0)
        {
            if (!path.Contains(defaultBundleSuffix))
            {
                path += defaultBundleSuffix;
            }
            return path;
        }

        public class BundleInfo
        {
            public AssetBundle AssetBundleHandle
            {
                get;
                set;
            }

            public UnityEngine.Object ObjectHandle
            {
                get;
                set;
            }

            public int ReferenceCount
            {
                get; set;
            }
        }
    }
}
